package bsh.meta;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import bsh.CompilationState;
import bsh.exception.CompilationAssertionFailed;

/**
 * @author takeshi
 */
public abstract class AbstractMethodDescriptor implements MethodDescriptor {

    /**
     * {@inheritDoc}
     */
    public String toDescription() {
        Type retType = Type.getType(this.returnType().typeDescription());
        List<? extends TypeWrapper> list = this.parameterTypes();
        Type[] params = new Type[list.size()];
        int i = 0;
        for (TypeWrapper typeWrapper : list) {
            params[i++] = Type.getType(typeWrapper.typeDescription());
        }
        return Type.getMethodDescriptor(retType, params);
    }

    /**
     * {@inheritDoc}
     */
    public void writeInvoke(MethodVisitor visitor, CompilationState state) {
        this.drainArgsCheckType(state);
        int opcode = getOpcode();
        if (opcode != Opcodes.INVOKESTATIC) {
            state.popStack();
        }
        visitor.visitMethodInsn(opcode, this.declaringType().internalName(), this.methodName(), this.toDescription());
        TypeWrapper type = this.returnType();
        if (!(type instanceof VoidTypeWrapper) && !type.equals(JavaClassWrapper.VOID_WRAPPER)) {
            state.pushStack(type);
        }
    }

    /**
     * @return the opcode used to invoke this method
     */
    protected int getOpcode() {
        if (this.isStatic()) {
            return Opcodes.INVOKESTATIC;
        } else if ("<init>".equals(this.methodName())) { // constructor
            return Opcodes.INVOKESPECIAL;
        } else {
            return this.declaringType().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL;
        }
    }

    /**
     * {@inheritDoc}
     */
    public final boolean isStatic() {
        return (this.modifiers() & Opcodes.ACC_STATIC) != 0;
    }

    /**
     * {@inheritDoc}
     */
    public List<TypeWrapper> drainArgsCheckType(CompilationState state) {
        List<? extends TypeWrapper> paramTypes = this.parameterTypes();
        final int size = paramTypes.size();
        ArrayList<TypeWrapper> list = new ArrayList<TypeWrapper>(size);
        for (int i = 0; i < size; i++) {
            final int index = size - i - 1;
            TypeWrapper expected = paramTypes.get(index);
            final TypeWrapper onStack = state.popStack();
            if (!expected.isSuperTypeOf(onStack)) {
                throw new CompilationAssertionFailed("expected: " + expected + " but was: " + onStack + " on position: " + index);
            }
            list.add(onStack);
        }
        Collections.reverse(list);
        return list;

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.declaringType().internalName() + " -> " + this.methodName() + " " + this.toDescription();
    }

}
